作為 architecture pattern 三兄弟最晚出生的小弟,MVVM (Model-View-ViewModel) 卻在 Android 平台上成為 Google 大力支持的後起之秀。
先讓我們來看看 MVVM 所定義的階層以及關係:
在 MVVM 的設計當中,依賴性的關係完全是單向的:View 認識 View Model、View Model 認識 Model,反向的關係則不存在。
階層當中的實線與虛線分別代表依賴以及觀察,View 直接依賴於 View Model 實體,呼叫其提供的介面。並透過觀察 View Model 的更新,來決定自己對應的行為;View Model 對 Model 也有著同樣的關係。
Model 本身可以完全獨立於其他兩個層級存在;而 View Model 也獨立於 View 存在,要更換 View 實作時不容易觸動到其他地方。
Model: 與另外兩個 patterns 當中的 Model 類似,需要提供可被觀察的介面。
View: 接收使用者輸入並通知 View Model;觀察 View Model 以更新畫面。
View Model: 在實現箭頭方向的部分 (依賴),負責接收使用者輸入,並轉換成對於 Model 的更新。在虛線箭頭方向的部分(觀察),將 Model 及畫面呈現所需的狀態 (view state) 封裝並提供可被 View 觀察的介面。
code example 中提供了三種 MVVM 的實作,分別為:
第一種實作只是自己在測試 Google 所提供的支援,主要想討論的是將情境 "分開或是集中" 的這個選擇。
在 MVVM 中,我們常會用一個 View Model 來封裝一個使用情境、或著對於同一個 Model 進行操作的多個情境。這裡以 "瀏覽單篇文章以及返回" 的情境舉例來說:在顯示單篇文章內容時出現返回鍵,這裡的兩個行為 "單篇文章的顯示或隱藏"、"返回按鍵的隱藏或顯示" 被視為同一個情境。
從圖中可看出,View Model 層處理的事情只有選擇文章的行為,而返回按鍵的隱藏或顯示則是完全由 View 層決定。
但如果有新的需求希望改變返回鍵的行為:
此時我們可能就會想要將原本的情境切割成兩個,並由不同的 View Model 來實現:
在這種實作中,BackViewModel 扮演了有趣的角色,在 "隱藏單篇文章" 的行為上,他更改了 Model 的值,這個結果會藉由 SelectedArticleViewModel 被 View 觀察到。
而在 "返回上一頁" 的行為上,則是直接被 View 觀察並執行。
比較這兩種不同的實作方式,我們可以看到雖然 View Model 有所不同,但需要的 Model 完全相同;在多個 View Model 的實作當中,各個 View Model 透過觀察而能夠隨時得知 Model 的最新狀態,彼此之間不需要有任何依賴以及溝通。
依賴關係
在 MVC (Model2) 或是 MVP 中,Controller, Presenter 需要扮演主動串流事件的角色,所以與 View 之間存在著依賴性。且就是因為這樣的關係,在擴充情境時很可能需要額外拉出更多高度依賴的連結,或是主動通知資料更新的行為,讓系統複雜程度持續上升。
MVVM 善加利用了觀察者模式,明確限制各層級之間的依賴關係以及溝通方式,以換取層級之間職責區分更加明確。
三個層級就像是 clean architecture 的洋蔥圖一般:Model 作為最核心的存在,對外層一無所知;View Model 層定義了各種使用情境以及其串流,包括預期從外層被呼叫的介面、以及對內層需要依賴的 Model;View 作為最外層,實作了所有畫面顯示和使用者輸入的需求。
一旦任何 Model 層的資料有所更新,所有相關的 View Model 便會自動運作起來,為自己的情境進行所需的更新。View Models 之間不需要知道彼此,也能順利的完成合作。
畫面狀態 (View State)
在 MVC 以及 MVP 當中,沒有明確定義畫面狀態的存在。在傳統的 MVC 當中,所有的狀態都需要創造 Model 來紀錄,而在 MVP 中,可能有部分畫面狀態會散落在 Presenter 內部。但在這兩種 patterns 的實作上,更多時候我們會不小心讓這些狀態散落在 View 當中。
相較之下 MVVM 將畫面狀態封裝在 View Model 層,提供了一個合理放置畫面狀態的地方,既不會污染 Model 也不會讓狀態到處散落。
優勢
缺陷
MVVM 在設計上有著相當多巧思,為其帶來了許多特別之處。他相當符合 clean architecture 的原則、同時支持關注分離及單向性、分層時的限制也為團隊合作帶來了規範。但此同時,我們也必須看到他所帶來的 trade-off,包括高學習成本,以及與實作選用方式的高依賴等等,都是需要整個團隊長期投入成本才能消化的。
雖然在 MVC, MVP, MVVM pattern 三篇文章中都描寫了優勢及缺陷,但對筆者來說他們並沒有絕對的好壞,在選用時還是會以 pattern 的特性、開發當下的需求、未來發展性以及維護成本、團隊組成等去思考。
而在這三個 architecture pattern 之外,也有著像是 MVI, Redux ... 等更多的選擇,相信未來也會持續出現各式各樣的 pattern。
總的來說,architecture pattern 們都希望能夠 "增加一定程度的限制,來換取開發時的一致性",透過定義 "哪個元件/階層的責任是什麼" 來限制元件的行為、以及階層之間的溝通方式。
開發團隊在選用 architecture pattern 合作時,一定程度的開發成本綁定在每位成員對於 pattern 本身的認知。所以在引用 pattern 時,需要盡量避免以自己的解讀破壞原有的限制,才能夠真正帶來團隊合作上的幫助。
三篇 architecture patterns 的介紹到此結束,希望有幫助大家發現一點新東西!
作者:Yolung